package com.thx.user.action;

import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

import javax.servlet.http.HttpSession;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;

import com.thx.common.struts2.BaseAction;
import com.thx.common.util.AppContext;
import com.thx.common.util.CaptchaUtil;
import com.thx.common.util.DateUtil;
import com.thx.common.util.PasswordUtil;
import com.thx.common.util.RsaUtils;
import com.thx.common.web.ActionResult;
import com.thx.common.web.WebConstant;
import com.thx.log.LogUtil;
import com.thx.user.model.User;
import com.thx.user.service.UserManager;
import com.thx.user.util.ModuleConstant;

/**
 * @author balance
 *
 */
@Namespace("/")
@Action(value = "login", results = {@Result(name = "login", location = "/pages/login.jsp"),
    @Result(name = "loginEncrypted", location = "/pages/system/login/loginEncrypted.jsp"),
    @Result(name = "success", location = "/pages/common/welcome.jsp"),
    @Result(name = "json", type = "json", params = {"root", "result"})})
@Controller
@Scope("prototype")
public class LoginAction extends BaseAction {

  private static final long serialVersionUID = -4706288390995847345L;

  // 运行模式是否为debug模式
  protected static boolean debug = Boolean.parseBoolean(AppContext
      .getProperties(WebConstant.LOGIN_DEBUG));

  // 登录表单提交信息是否加密传输
  protected static boolean loginEncrypted = Boolean.parseBoolean(AppContext
      .getProperties(WebConstant.LOGIN_ENCRYPTED));

  protected static final String PRIVATE_KEY = "private_key_in_session";

  protected static final String PUBLIC_KEY = "public_key";

  protected static final String LOGIN_ERROR_COUNT = "failedCount";

  protected static final String LOGIN_LOCK_DAY = "lazyLockDay";

  protected static final String LOGIN_PWD_LOCK_DAY = "lazyPwdLockDay";

  protected static final String LOGIN_PWD_ALTER_DAY = "pwdFailAlterDay";

  protected String loginName;

  protected String passwd;

  protected String captcha;

  protected String publicKeyExponent;

  protected String publicKeyModulus;

  @Autowired
  protected UserManager userMgr;


  public String execute() {
    if (isLogined())
      return SUCCESS;
    else if (loginName == null && passwd == null && captcha == null)
      return input();
    else
      return logon();
  }


  public String input() {

    if (debug) {
      loginName = "test";
      passwd = "test";
      captcha = "debug";
    }

    int lazyLockDay = -1;
    try {
      lazyLockDay = Integer.parseInt(AppContext.getProperties(LOGIN_LOCK_DAY));
    } catch (Exception e) {
      logger.error("初始化密码有效期天数异常:" + e.getMessage());
    }
    if (lazyLockDay != -1) {
      message = "友情提示:账户密码有效期为" + lazyLockDay + "天，请及时修改！";
    }
    if (loginEncrypted) {
      // TODO 生成公私钥，私钥放入session中，公钥传入页面供js加密用
      HashMap<String, Object> map;
      try {
        map = RsaUtils.getKeys();
        // 生成公钥和私钥
        RSAPublicKey publicKey = (RSAPublicKey) map.get("public");
        RSAPrivateKey privateKey = (RSAPrivateKey) map.get("private");

        getSession().setAttribute(PRIVATE_KEY, privateKey);// 私钥保存在session中，用于解密

        // 公钥信息保存在页面，用于加密
        publicKeyExponent = publicKey.getPublicExponent().toString(16);
        publicKeyModulus = publicKey.getModulus().toString(16);
      } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return "login";
      }
      return "loginEncrypted";
    } else
      return "login";
  }

  public User findUserByLoginName() {
    loginName = org.apache.commons.lang3.StringUtils.strip(loginName);

    User user = null;
    if (loginName.matches("^[a-zA-Z_\\u4e00-\\u9fa5]+[\\w\\u4e00-\\u9fa5]*$")) {
      user = userMgr.findByName(loginName);
    } else if (loginName.matches("^1\\d{10}$")) {
      user = userMgr.findByPhone(loginName);
    } else if (loginName.indexOf("@") != -1) {
      user = userMgr.findByEmail(loginName);
    }
    return user;
  }

  /**
   * 验证登陆名是否存在
   *
   * @return
   */
  public void checkUserName() {
    try {
      User user = findUserByLoginName();
      if (user == null)
        writeJson("false");
      else
        writeJson("true");
    } catch (Exception e) {
      setResult(ActionResult.FAIL, "服务器异常！");
    }
  }

  /**
   * 登录验证
   *
   * @return
   */
  public String logon() {

    if (!validateCaptcha()) {// 首先验证验证码是否正确
      setResult("captchaError", "验证码错误！");
    } else {
      try {
        User user = findUserByLoginName();
        if (user != null) {
          // 密码有效期天数
          int lazyLockDay = -1;
          // 允许密码输入错误次数
          int failedCount = 0;
          // 密码输入错误账号锁定天数
          int lazyPwdLockDay = 0;
          try {
            lazyLockDay = Integer.parseInt(AppContext.getProperties(LOGIN_LOCK_DAY));
          } catch (Exception e) {
            logger.error("初始化密码有效期天数异常:" + e.getMessage());
          }
          try {
            lazyPwdLockDay = Integer.parseInt(AppContext.getProperties(LOGIN_PWD_LOCK_DAY));
          } catch (Exception e) {
            logger.error("初始化密码输入错误账号锁定天数异常:" + e.getMessage());
          }
          try {
            failedCount = Integer.parseInt(AppContext.getProperties(LOGIN_ERROR_COUNT));
          } catch (Exception e) {
            logger.error("初始化允许密码输入错误次数异常:" + e.getMessage());
          }

          Date nowDate = new Date();
          // 在过了锁定期后重置登录状态数据
          if (checkLoginDate(user.getFailedDate(), lazyPwdLockDay - 1, nowDate)) {
            userMgr.saveloginErrorInfo(user.getUserId(), 0, null);
            user.setFailedCount(0);
            user.setFailedDate(null);
            logger.info("用户[" + loginName + "]账户锁定已超过" + lazyPwdLockDay + "天，自动解锁");
          }

          if (failedCount != 0 && user.getFailedCount() >= failedCount) {
            setResult(ActionResult.FAIL, "您已超过" + failedCount + "次密码输入错误，账号锁定！");
          } else if (checkLoginDate(user.getLastPwdChange(), lazyLockDay, nowDate)) {
            setResult(ActionResult.FAIL, "您账户密码已超过" + lazyLockDay + "天未修改，密码失效，请及时修改！");
          } else if (!checkpw(user.getPassword(), user.getSalt())) {
            /**
             * 密码输入错误，记录时间和次数 Xuejw 2016-05-10
             */
            userMgr.saveloginErrorInfo(user.getUserId(), user.getFailedCount() + 1, nowDate);
            setResult(ActionResult.FAIL, "密码错误！");
          } else {
            user.getRoles().size();//触发角色的懒加载
            HttpSession session = getSession();
            session.setAttribute(WebConstant.KEY_USER, user);
            /**
             * 登录成功，记录最后登录时间，清除登录失败记录
             */
            userMgr.saveloginSuccessInfo(user.getUserId(), new Date());
            setResult(ActionResult.SUCCESS, user.getRealName());
            LogUtil.log(ModuleConstant.SYSTEM_MANAGER, LogUtil.ACTION_LOGIN,
                LogUtil.RESULT_SUCCESS, "登录系统");
          }
        } else {
          setResult(ActionResult.FAIL, "用户名不存在！");
        }
      } catch (Exception e) {
        setResult(ActionResult.FAIL, "服务器异常！");
      }
    }
    return JSON;
  }

  /**
   * 验证码是否正确
   *
   * @param session
   * @param authCode
   * @return
   */
  protected boolean validateCaptcha() {
    boolean debug = Boolean.parseBoolean(AppContext.getProperties(WebConstant.LOGIN_DEBUG));
    if (debug)
      return true;
    HttpSession session = getSession();
    String serverCaptcha = (String) session.getAttribute(CaptchaUtil.CAPTCHA_NAME);
    if (StringUtils.isEmpty(serverCaptcha) || StringUtils.isEmpty(captcha))
      return false;
    invalidateCaptcha();// 验证码只能使用一次，每次登录请求前都需要更换新的验证码，否则起不到防暴力破解密码的漏洞
    return serverCaptcha.equalsIgnoreCase(org.apache.commons.lang3.StringUtils.strip(captcha));
  }

  /**
   * 让验证码失效
   */
  protected void invalidateCaptcha() {
    getSession().removeAttribute(CaptchaUtil.CAPTCHA_NAME);
  }

  protected boolean isLogined() {
    HttpSession session = getSession();
    User user = (User) session.getAttribute(WebConstant.KEY_USER);
    return user != null;
  }

  protected boolean checkpw(String hashed, String salt) {
    if (hashed == null || hashed.equals("") || passwd == null || passwd.equals(""))
      return false;

    if (loginEncrypted) {
      // TODO 从session中取出私钥，对密码进行解密
      RSAPrivateKey privateKey = (RSAPrivateKey) getSession().getAttribute(PRIVATE_KEY);
      String descrypedPwd = null;
      try {
        descrypedPwd = RsaUtils.decryptByPrivateKey(passwd, privateKey);
      } catch (Exception e) {
        e.printStackTrace();
      }
      passwd = descrypedPwd;
    }
    return PasswordUtil.isMatch(passwd, hashed, salt);
  }

  /**
   * 获取密码有效期到期时间信息
   *
   * @author Xuejw 2016年5月12日 下午2:37:04
   */
  public String getPwdFailDate() {
    // 密码有效期提前提醒天数
    int pwdFailAlterDay = 0;
    // 密码有效期
    int lazyLockDay = 0;
    try {
      lazyLockDay = Integer.parseInt(AppContext.getProperties(LOGIN_LOCK_DAY));
      pwdFailAlterDay = Integer.parseInt(AppContext.getProperties(LOGIN_PWD_ALTER_DAY));
      User user = (User) getSession().getAttribute(WebConstant.KEY_USER);
      Date d = getEndDate(user.getModifyDate(), lazyLockDay - pwdFailAlterDay);
      if (checkLoginDate(user.getModifyDate(), lazyLockDay - pwdFailAlterDay, new Date())) {
        d = getEndDate(user.getModifyDate(), lazyLockDay);
        setResult(ActionResult.SUCCESS, DateUtil.format(d, "yyyy-MM-dd"));
      }
    } catch (Exception e) {
      logger.error("获取密码有效期到期时间信息异常:" + e.getMessage());
    }
    return JSON;
  }

  /**
   * 检测当前日期距指定时间是否在有效时间内
   *
   * @author Xuejw 2016年5月10日 上午10:59:05
   * @param lastLoginDate 指定时间
   * @param dayCount 有效时间天数
   * @param nowDay 当前日期
   * @return true 时间超时 ,dayCount<0时返回false
   */
  public Boolean checkLoginDate(Date lastLoginDate, int dayCount, Date nowDay) {
    try {
      if (dayCount >= 0 && lastLoginDate != null) {
        return getEndDate(lastLoginDate, dayCount).before(nowDay);
      }
    } catch (Exception e) {
      logger.error("检测当前日期是否失效", e);
    }
    return false;
  }

  /**
   * 获取有效期的最后日期
   *
   * @param lastDate
   * @param dayCount
   * @return
   * @throws Exception
   */
  private Date getEndDate(Date lastDate, int dayCount) throws Exception {
    Calendar c = Calendar.getInstance();// 获得一个日历的实例
    c.setTime(lastDate);
    c.add(Calendar.DATE, dayCount);
    c.set(Calendar.HOUR_OF_DAY, 23);// Calendar.HOUR_OF_DAY是24小时制 Calendar.HOUR是12小时制
    c.set(Calendar.MINUTE, 59);
    c.set(Calendar.SECOND, 59);
    return c.getTime();
  }

  // ==================以下是“属性”get set 方法代码区========================

  public String getPasswd() {
    return passwd;
  }

  public String getLoginName() {
    return loginName;
  }

  public void setLoginName(String loginName) {
    this.loginName = loginName;
  }

  public void setPasswd(String passwd) {
    this.passwd = passwd;
  }

  public String getCaptcha() {
    return captcha;
  }

  public void setCaptcha(String captcha) {
    this.captcha = captcha;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public String getPublicKeyExponent() {
    return publicKeyExponent;
  }

  public void setPublicKeyExponent(String publicKeyExponent) {
    this.publicKeyExponent = publicKeyExponent;
  }

  public String getPublicKeyModulus() {
    return publicKeyModulus;
  }

  public void setPublicKeyModulus(String publicKeyModulus) {
    this.publicKeyModulus = publicKeyModulus;
  }

}
